package se.perp.c4.net;

import java.util.Map;
import se.perp.c4.model.Actor.EActor;
import se.perp.c4.model.Actor.Side;
import se.perp.c4.model.Territory.ETerritory;
import se.perp.c4.model.Card.ECard;
import se.perp.c4.model.Game.ShelobRule;
import se.perp.c4.model.Move;

public final class Message {

    private static final String PREFIX_NAME = "n";
    private static final String PREFIX_PLACEMENT = "p";
    private static final String PREFIX_DATA = "i";
    private static final String DISCONNECT_KEY = "xkillmex";

    public static enum Type {

        NAME, SHELOB_RULE, SIDE, PLACEMENT,
        MOVE, BATTLE_MOVE, RECALL_MOVE, RETREAT_MOVE,
        CARD,
        PIPPIN_RETREAT_CHOICE,
        SARUMAN_PLAYS_CARDS, FRODO_INSTARETREATS, SAM_REPLACES_FRODO, PIPPIN_RETREATS,
        SHADOWFAX_PLAYED, PALANTIR_PLAYED, GANDALF_WHITE_PLAYED,
        PEER_IS_DISCONNECTING
    }
    private final Type type;
    private final String stringData;
    private Object payload = null;

    static Message fromString(String s) {
        if (s == null || s.length() < 2) {
            throw new IllegalArgumentException(s);
        }

        char c = s.charAt(0);
        if (c == 'n') {
            return new Message(Type.NAME, s);
        } else if (c == 'p') {
            return new Message(Type.PLACEMENT, s);
        } else if (c == 'i') {
            int iType = Integer.valueOf(s.substring(1, 3), 16);
            Type type = Type.values()[iType];

            return new Message(type, s);
        } else if (s.equals(DISCONNECT_KEY)) {
            return new Message(Type.PEER_IS_DISCONNECTING, s);
        } else {
            throw new IllegalArgumentException(s);
        }
    }

    private static Object payloadFromString(String s) {
        if (s.startsWith(PREFIX_NAME)) {
            return s.substring(1);
        } else if (s.startsWith(PREFIX_PLACEMENT)) {
            return Codec.decodePlacement(s.substring(1));
        } else if (s.startsWith(PREFIX_DATA)) {
            int itype = Integer.valueOf(s.substring(1, 3), 16);
            int payload = Integer.valueOf(s.substring(3), 16);

            Type type = Type.values()[itype];
            switch (type) {
                case PEER_IS_DISCONNECTING:
                    return new Object();
                case SHELOB_RULE:
                    return Codec.decodeShelobRule(payload);
                case SIDE:
                    return Codec.decodeSide(payload);
                case MOVE:
                    return Codec.decodeMove(payload);
                case BATTLE_MOVE:
                    return Codec.decodeMove(payload);
                case RETREAT_MOVE:
                    return Codec.decodeMove(payload);
                case RECALL_MOVE:
                    return Codec.decodeMove(payload);
                case CARD:
                    return Codec.decodeCard(payload);
                case PIPPIN_RETREAT_CHOICE:
                    return Codec.decodeTerritory(payload);
                case PIPPIN_RETREATS:
                    return Codec.decodeBoolean(payload);
                case SARUMAN_PLAYS_CARDS:
                    return Codec.decodeBoolean(payload);
                case SAM_REPLACES_FRODO:
                    return Codec.decodeBoolean(payload);
                case FRODO_INSTARETREATS:
                    return Codec.decodeBoolean(payload);
                case SHADOWFAX_PLAYED:
                    return new Object();
                case PALANTIR_PLAYED:
                    return Codec.decodeTerritory(payload);
                case GANDALF_WHITE_PLAYED:
                    return new Object();
                default:
                    throw new IllegalArgumentException(s);
            }
        } else {
            throw new IllegalArgumentException(s);
        }
    }

    public static Message newShelobRule(ShelobRule rule) {
        return new Message(Type.SHELOB_RULE, Codec.encodeShelobRule(rule));
    }

    public static Message newName(String name) {
        return new Message(Type.NAME, PREFIX_NAME + name);
    }

    public static Message newPlacement(Map<EActor, ETerritory> placement) {
        return new Message(Type.PLACEMENT, PREFIX_PLACEMENT + Codec.encodePlacement(placement));
    }

    public static Message newSide(Side side) {
        return new Message(Type.SIDE, Codec.encodeSide(side));
    }

    public static Message newMove(Move move) {
        return new Message(Type.MOVE, Codec.encodeMove(move));
    }

    public static Message newBattleMove(Move move) {
        return new Message(Type.BATTLE_MOVE, Codec.encodeMove(move));
    }

    public static Message newRetreatMove(Move move) {
        return new Message(Type.RETREAT_MOVE, Codec.encodeMove(move));
    }

    public static Message newRecallMove(Move move) {
        return new Message(Type.RECALL_MOVE, Codec.encodeMove(move));
    }

    public static Message newCard(ECard ecard) {
        return new Message(Type.CARD, Codec.encodeCard(ecard));
    }

    public static Message newPippinRetreatChoice(ETerritory et) {
        return new Message(Type.PIPPIN_RETREAT_CHOICE, Codec.encodeTerritory(et));
    }

    public static Message newPippinRetreats(boolean b) {
        return new Message(Type.PIPPIN_RETREATS, Codec.encodeBoolean(b));
    }

    public static Message newSarumanPlaysCards(boolean b) {
        return new Message(Type.SARUMAN_PLAYS_CARDS, Codec.encodeBoolean(b));
    }

    public static Message newFrodoInstaretreats(boolean b) {
        return new Message(Type.FRODO_INSTARETREATS, Codec.encodeBoolean(b));
    }

    public static Message newSamReplacesFrodo(boolean b) {
        return new Message(Type.SAM_REPLACES_FRODO, Codec.encodeBoolean(b));
    }

    public static Message newShadowfaxPlayed() {
        return new Message(Type.SHADOWFAX_PLAYED, 0);
    }

    public static Message newPalantirPlayed(ETerritory where) {
        return new Message(Type.PALANTIR_PLAYED, Codec.encodeTerritory(where));
    }

    public static Message newGandalfWhitePlayed() {
        return new Message(Type.GANDALF_WHITE_PLAYED, 0);
    }

    static Message newDisconnect() {
        return new Message(Type.PEER_IS_DISCONNECTING, DISCONNECT_KEY);
    }

    private Message(Type type, int intData) {
        this.type = type;
        stringData = PREFIX_DATA + String.format("%02x%x", type.ordinal(), intData);
    }

    private Message(Type type, String stringData) {
        this.type = type;
        this.stringData = stringData;
    }

    String getStringData() {
        return stringData;
    }

    public Type getType() {
        return type;
    }

    public Object getPayload() {
        if (payload == null) {
            payload = payloadFromString(stringData);
        }
        return payload;
    }

    @Override
    public String toString() {
        return type.toString() + " [" + stringData + "]";
    }

}
